package jmine.tec.utils.io.http.impl;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
 * Fake socket factory to bypass SSL certificate authentication. Creates connection sockets for secured connections without certification
 * check.
 * 
 * @author joao.enomoto
 */
public class SSLFakeSocketFactory extends SSLSocketFactory implements HostnameVerifier {

    /**
     * Determines a {@link SSLContext} from which a {@link SSLSocketFactory} will create {@link Socket}s.
     * 
     * @return context for SSL connections
     * @throws Exception any exception
     */
    private SSLContext getContext() throws Exception {
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, new TrustManager[]{ new FakeTrustManager() }, new SecureRandom());
        return sc;
    }

    /**
     * {@inheritDoc}
     */
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        try {
            return this.getContext().getSocketFactory().createSocket(host, port);
        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("Error by initializing socket", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException, UnknownHostException {
        try {
            return this.getContext().getSocketFactory().createSocket(host, port, localAddress, localPort);
        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("Error by initializing socket", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        try {
            return this.getContext().getSocketFactory().createSocket(socket, host, port, autoClose);
        } catch (UnknownHostException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("Error by initializing socket", e);
        }
    }

    /**
     * Mock {@link X509TrustManager} which determines that any client and server is trusted.
     * 
     * @author joao.enomoto
     */
    private class FakeTrustManager implements X509TrustManager {

        /**
         * {@inheritDoc}
         */
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        /**
         * {@inheritDoc}
         */
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // não faz nada
        }

        /**
         * {@inheritDoc}
         */
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // não faz nada
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String[] getDefaultCipherSuites() {
        return new String[]{};
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String[] getSupportedCipherSuites() {
        return new String[]{};
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return this.createSocket(host.getHostName(), port);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return this.createSocket(address.getHostName(), port, localAddress, localPort);
    }

    /**
     * {@inheritDoc}
     */
    public boolean verify(String arg0, SSLSession arg1) {
        return true;
    }

    /**
     * Registra o {@link SSLSocketFactory}
     */
    public static void register() {
        new SSLFakeSocketFactory().initialize();
    }

    /**
     * Se registra como um SSLSocketFactory
     */
    public void initialize() {
        HttpsURLConnection.setDefaultSSLSocketFactory(this);
        HttpsURLConnection.setDefaultHostnameVerifier(this);
    }

}
